-- proj: 一些常用通用的接口
-- date: 2022/7/12
-- auth: zour
local uu = require "modules.commhm.utils"
local mis_config = require "mis_config"
local log = require "modules.commhm.log"
local ns_network = require('modules.commhm.network')
local mis_data = require "mis_data"
local t_cache = ngx.shared.misTime
local t_retCode = mis_config.t_retCode
local mis_env = require('modules.env.env')

local M = {}
local MIS_TOOLS = "mis_tools"


--奖励状态
M.REWARDS_STATUS = {
    NONE = 0,       --0不可领
    READY = 1,      --1可领取
    ALREADY = 2,    --2已领取
}

--兑换商店
M.EXCHANGE_SHOP= {
    READY = 0,       --正常可兑换
    ALREADY = 1,     --已拥有
    NO_COUNT = 2,    --兑换次数不足 
}

--ITEM_ID
M.REWARDS_ID = {
    SANRIO = 1,       --三丽鸥积分
    MOON = 2,         --中秋节月饼
    NATIONAL = 3,     --国庆星之代币
}



--=====================================================================================
--三丽鸥里发给账号服记录的ID： 1为三丽鸥积分 2为月饼  3国庆星之代币
--三丽鸥新增的任务类型ID： 1001拥有尼姆皮肤 1002赏月地图拍照并分享动态
	--1003点亮N轮拼图  1004购买N次月饼礼盒 1005打卡某张地图


--date转ts
M.data2ts = function (date)
	if not date then 
		return uu.now()
	end

	local n1,n2,n3 = string.match(date, "(%d+)-(%d+)-(%d+)")
	local h, m, s = string.match(date, "(%d+):(%d+):(%d+)")
	return os.time({year=n1, month=n2,day=n3, hour=h, min=m, sec=s})
end

--检查总控
M.check_control = function (t_conf)
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.control~=nil and (not t_conf.ctrl_cfg.control) then 
		return false
	end

	return true
end

--检查时间控制
M.check_time = function (t_conf)
	local start_ts
	local end_ts

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.start_time then 
		start_ts = M.data2ts(t_conf.ctrl_cfg.start_time)
	end

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.end_time then 
		end_ts = M.data2ts(t_conf.ctrl_cfg.end_time)
	end

	local now_time = uu.now()
	if start_ts then 
		if start_ts > now_time then 
			return false
		end
	end

	if end_ts then 
		if end_ts < now_time then 
			return false
		end
	end
	
	return true
end

--检查版本
M.check_version = function (t_conf, ver)
	local ver_min
	local ver_max

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.version_min then 
		ver_min = uu.ver2i(t_conf.ctrl_cfg.version_min)

		--log.debug("t_conf.ctrl_cfg.version_min="..(t_conf.ctrl_cfg.version_min))
		--log.debug("ver_min="..ver_min)
	end

	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.version_max then 
		ver_max = uu.ver2i(t_conf.ctrl_cfg.version_max)

		--log.debug("t_conf.ctrl_cfg.version_max="..(t_conf.ctrl_cfg.version_min))
		--log.debug("ver_max="..ver_max)
	end
	local n_ver = uu.ver2i(ver)

	--log.debug("ver="..ver)
	--log.debug("n_ver="..n_ver)

	--
	if ver_min then 
		if n_ver < ver_min then 
			return false
		end
	end

	if ver_max then 
		if n_ver > ver_max then 
			return false
		end
	end

	return true
end

--检查渠道限制
M.check_apiid = function (t_conf, check_apiid)
	--log.debug("t_conf.ctrl_cfg.apiids_no="..(t_conf.ctrl_cfg.apiids_no or "nil"))
	--log.debug("t_conf.ctrl_cfg.ap2222="..type(t_conf.ctrl_cfg.apiids_no))
	--log.debug("check_apiid="..(check_apiid or "nil"))
	--若有不允许的渠道
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.apiids_no then 
		for n in string.gmatch(t_conf.ctrl_cfg.apiids_no, "([^%,]+)") do
			--log.debug("apiids_no="..(tonumber(n) or "nil"))
    		if tonumber(check_apiid)==tonumber(n) then 
				return false
			end
		end
	end

	--若有渠道控制
	if t_conf.ctrl_cfg and t_conf.ctrl_cfg.apiids then 
		local find = false
		for n in string.gmatch(t_conf.ctrl_cfg.apiids, "([^%,]+)") do
			--log.debug("apiids="..(tonumber(n) or "nil"))
    		if tonumber(check_apiid)==tonumber(n) then 
				find = true
			end
		end
		if find then 
			return true
		else
			return false
		end
	end

	return true
end

--对总控，渠道，版本，时间都做校验
M.check_limit = function (t_conf, check_apiid, ver)
	if not t_conf then 
		return false, "check t_conf error"
	end

	if M.check_control(t_conf)==false then 
		return false, "check_control false"
	end

	if M.check_apiid(t_conf, check_apiid)==false then 
		return false, "check_apiid false"
	end

	if M.check_version(t_conf, ver)==false then 
		return false, "check_version false"
	end

	if M.check_time(t_conf)==false then 
		return false, "check_time false"
	end

	return true
end

M.get_day_zero = function (ts)
	ts = ts or uu.now()
	--取年月日， 时分秒给0，即得当天0点时间戳
	local ts_table = os.date("*t", ts)
	local zero_ts = os.time({year=ts_table.year, month=ts_table.month, day=ts_table.day,
		hour=0, min=0, sec=0})

	return zero_ts
end

M.check_day_delta = function(day1, other_day)
	local day1_zero = M.get_day_zero(day1)
	
	local delta = math.abs(other_day - day1_zero)
	local delta_f = tonumber(string.format("%.2f", delta/86400))

	return delta/86400
end

--检测任务复位
M.check_task_reset = function (task)
	if not task.finish_ts then 
		return true
	end
	
	local delta = M.check_day_delta(task.finish_ts, uu.now())
	if delta >= tonumber(task.daily) then 
		return true
	end

	return false
end

--检测任务复位
M.check_task_reset_v2 = function (task)
	if not task.the_start_ts then 
		return true
	end
	
	local delta = M.check_day_delta(task.the_start_ts, uu.now())
	if delta >= tonumber(task.daily) then 
		return true
	end

	return false
end

---------------------------------------------------------------
local function update_task_conf(t_task_conf, data_info)
	--data_info必须由外层传过来
	if not data_info then 
		return 
	end

	if not t_task_conf then
		t_task_conf = {}	
	end

    if not data_info.total_score then 
        data_info.total_score = 0
    end

    --普通奖励
    if t_task_conf.normal_rewards and (#t_task_conf.normal_rewards>0) then 
        if not data_info.normal_reward then 
            data_info.normal_reward = {}
        end 
        for k, v in ipairs(t_task_conf.normal_rewards) do
            data_info.normal_reward[k] = data_info.normal_reward[k] or {}
            data_info.normal_reward[k].item_id = v.item_id
            data_info.normal_reward[k].item_nums = v.item_nums
            data_info.normal_reward[k].item_score = v.item_score
            data_info.normal_reward[k].item_status = data_info.normal_reward[k].item_status or M.REWARDS_STATUS.NONE
        end
    end 

    --进阶奖励
    if t_task_conf.advance_rewards and (#t_task_conf.advance_rewards>0)then 
        if not data_info.advance_rewards then 
            data_info.advance_rewards = {}
        end 
        for k, v in ipairs(t_task_conf.advance_rewards) do
            data_info.advance_rewards[k] = data_info.advance_rewards[k] or {}
            data_info.advance_rewards[k].item_id = v.item_id
            data_info.advance_rewards[k].item_nums = v.item_nums
            data_info.advance_rewards[k].item_score = v.item_score
            data_info.advance_rewards[k].item_status = data_info.advance_rewards[k].item_status or M.REWARDS_STATUS.NONE
        end
    end

    if not data_info.events_finish_times then
        data_info.events_finish_times = {} 
    end
    if not data_info.task_list then 
        data_info.task_list = {}
    end 
	 
	local daily_event_id = {}
    --任务数据
    for task_id, v in ipairs(t_task_conf.task_list or {}) do
        data_info.task_list[task_id] = data_info.task_list[task_id] or {}
        data_info.task_list[task_id].task_id = task_id
        data_info.task_list[task_id].name = v.name
        data_info.task_list[task_id].daily = v.daily
        data_info.task_list[task_id].event = v.event
        data_info.task_list[task_id].target = tonumber(v.target)
        data_info.task_list[task_id].bonus = v.bonus
		data_info.task_list[task_id].taskType = v.taskType --有的任务有任务类型 
		data_info.task_list[task_id].special_key = tonumber(v.special_key)	--特殊字段，如指定某皮肤
        data_info.task_list[task_id].status = data_info.task_list[task_id].status or M.REWARDS_STATUS.NONE
		data_info.task_list[task_id].report_id = v.reportId
        data_info.events_finish_times[v.event] = data_info.events_finish_times[v.event] or 0
		if v.taskType==1 and v.daily then 
			daily_event_id[tonumber(v.event)] = true
		end
	end

	--检测复位任务和挑战任务共同使用同一个event_id时
	for event_id, v in pairs(daily_event_id) do
		for _, task in pairs(data_info.task_list) do
			if (task.taskType==0) 
			and (tonumber(event_id)==tonumber(task.event)) then 
				if not data_info.spec_finish_times then 
					data_info.spec_finish_times = {}
				end
				data_info.spec_finish_times[task.event] = data_info.spec_finish_times[task.event] or 0
			end
		end
	end
end

M.update_task_info = function(uin, t_task_conf, data_info)

    update_task_conf(t_task_conf, data_info)

    --检测 按时间复位的任务
    for _, v in ipairs(data_info.task_list) do
		if v.daily and v.status~=M.REWARDS_STATUS.NONE then 
			if M.check_task_reset(v) then 
				v.finish_ts = nil
				v.status = M.REWARDS_STATUS.NONE
				data_info.events_finish_times[v.event] = 0
			end
		end
    end

    for _, v in ipairs(data_info.task_list) do
		--触发 每日登录任务
        if v.event==18 and v.status==M.REWARDS_STATUS.NONE then
            M.update_task(18, 1, data_info)
            break
        end

		--是否拥有指定皮肤
		if (v.event==1001) and (v.special_key) and (v.status==M.REWARDS_STATUS.NONE)  then 
			--查皮肤
			local iret_skin = false
			local skin_id = M.item_to_skin(v.special_key)
			if skin_id then 
				iret_skin = M.query_have_skin(uin, skin_id)
			end
			--查物品
			local iret_item = false
			local count = M.query_account_item_num(uin, v.special_key)
			if count>0 then 
				iret_item = true
			end
			if iret_skin or iret_item then 
				M.update_task(1001, 1, data_info, nil, v.special_key)
			end
		end

		--增加的count计数字段
		v.count = data_info.events_finish_times[v.event]
		if data_info.spec_finish_times and  data_info.spec_finish_times[v.event] then 
			v.count = data_info.spec_finish_times[v.event]
		end
		--任务计数已超过，则不再计数
		if v.count > v.target then 
			v.count = v.target
		end
    end
end

--未完成任务也需要复位，不能以finish_ts做判断
M.update_task_info_v2 = function(uin, t_task_conf, data_info)

    update_task_conf(t_task_conf, data_info)

    --检测 按时间复位的任务
    for _, v in ipairs(data_info.task_list) do
		if v.daily then 
			if M.check_task_reset_v2(v) then 
				v.the_start_ts = uu.now()
				v.status = M.REWARDS_STATUS.NONE
				data_info.events_finish_times[v.event] = 0
				v.count = 0
			end
		end
    end

    for _, v in ipairs(data_info.task_list) do
		--触发 每日登录任务
        if v.event==18 and v.status==M.REWARDS_STATUS.NONE then
            M.update_task(18, 1, data_info)
            break
        end

		--是否拥有指定皮肤
		if (v.event==1001) and (v.special_key) and (v.status==M.REWARDS_STATUS.NONE)  then 
			--查皮肤
			local iret_skin = false
			local skin_id = M.item_to_skin(v.special_key)
			if skin_id then 
				iret_skin = M.query_have_skin(uin, skin_id)
			end
			--查物品
			local iret_item = false
			local count = M.query_account_item_num(uin, v.special_key)
			if count>0 then 
				iret_item = true
			end
			if iret_skin or iret_item then 
				M.update_task(1001, 1, data_info, nil, v.special_key)
			end
		end

		--增加的count计数字段
		v.count = data_info.events_finish_times[v.event]
		if (not v.daily) and data_info.spec_finish_times and data_info.spec_finish_times[v.event] then 
			v.count = data_info.spec_finish_times[v.event]
		end
		--任务计数已超过，则不再计数
		if v.count >= v.target then 
			v.count = v.target
			--容错
			if v.status==M.REWARDS_STATUS.NONE then 
				v.status = M.REWARDS_STATUS.READY
			end
		end
    end
end

--上报某个任务，更新当前完成进度
--参数: 
    --times：完成次数 [每日登录X分钟，就需要上报X，如5分钟就传5；]
    --event:事件类型：该任务配置表上的event类型
--返回值：0 or 错误码
M.update_task = function(event_id, count, data_info, t_conf, special_key)
    if (not event_id) or (not count) or (not data_info) then 
		return false, "param error"
	end

	--
	if not data_info.events_finish_times then 
		update_task_conf(t_conf, data_info)
	end

	if not data_info.events_finish_times[event_id] then 
		data_info.events_finish_times[event_id] = 0
	end
    data_info.events_finish_times[event_id] = data_info.events_finish_times[event_id] + count

    local add_score = false
    local rewards = {}
    --增加总分并且对任务改变状态
    for _, v in pairs(data_info.task_list or {}) do
        if (v.status==M.REWARDS_STATUS.NONE) 
		and (event_id==v.event) 
		and (data_info.events_finish_times[event_id]>=v.target) then 
			--对拥有指定皮肤做特殊处理
			if (event_id==1001) or (event_id==1005) then 
				if special_key and v.special_key and (special_key==v.special_key) then 
					v.status = M.REWARDS_STATUS.READY
					v.finish_ts = uu.now()
					add_score = true
					table.insert(rewards, {id=v.bonus.itemId, num=v.bonus.num })
				end
			else
            	v.status = M.REWARDS_STATUS.READY
            	v.finish_ts = uu.now()
            	add_score = true
				table.insert(rewards, {id=v.bonus.itemId, num=v.bonus.num })
			end
        end
    end 

	--针对日常任务和挑战任务eventid一致时特殊处理挑战任务
	if data_info.spec_finish_times and data_info.spec_finish_times[event_id] then 
		data_info.spec_finish_times[event_id] = data_info.spec_finish_times[event_id] + count
		for _, v in pairs(data_info.task_list or {}) do
			if (v.status==M.REWARDS_STATUS.NONE) 
			and (v.taskType==0) 
			and (v.daily==nil)
			and (event_id==v.event) 
			and (data_info.spec_finish_times[event_id]>=v.target) then 
				if (event_id==1001) or (event_id==1005) then 
					if special_key and v.special_key and (special_key==v.special_key) then 
						v.status = M.REWARDS_STATUS.READY
						v.finish_ts = uu.now()
						add_score = true
						table.insert(rewards, {id=v.bonus.itemId, num=v.bonus.num })
					end
				else
					v.status = M.REWARDS_STATUS.READY
					v.finish_ts = uu.now()
					add_score = true
					table.insert(rewards, {id=v.bonus.itemId, num=v.bonus.num })
				end
			end
		end
	end

    if not add_score then 
        return false, '[finish] no finish task!'
    end

    return true, rewards
end

-- 说明：转为36进制字符
-- 字符范围：0-9-A-Z
local c_to_uint36 = function(n_num)
	if 0 <= n_num and n_num <= 9 then
		return '' .. n_num
	elseif 10 <= n_num and n_num <= 35 then
		return string.char(n_num + 55)
	else
		--log.debug("invalid num: " .. n_num)
	end
end

-- 说明：转为36进制数
-- 字符范围：0-9-A-Z
local function to_uint36 (n_num, length)
	n_num = tonumber(n_num) or 0
	length = tonumber(length) or 0

	local n_r
	local t_bit = {}
	while n_num > 35 do
		n_r = n_num % 36
		n_num = math.floor(n_num / 36)
		table.insert(t_bit, 1, c_to_uint36(n_r))
	end
	table.insert(t_bit, 1, c_to_uint36(n_num))

	local s_num = table.concat(t_bit, "")
	if s_num and length and #s_num < length then
		local s_fmt = "%0" .. (length - #s_num) .. "d%s"
		s_num = string.format(s_fmt, 0, s_num)
	end

	return s_num
end

-- 说明：生成分享密码
local function new_share_code(s_type, t_prefix, n_init, n_max)
	local n_MAX_CODE = 60466175
	local n_INIT_CODE = 38032637

	if #t_prefix == 0 then
		return nil, "prefix empty"
	end
	local s_codeKey = s_type .. "_share_code"
	local n_val = t_cache:incr(s_codeKey, 1, n_init or n_INIT_CODE)
	if n_val > (n_max or n_MAX_CODE) then
		-- 重置密码
		local b_ok, s_err = t_cache:add(s_codeKey .. "_lock", 1, 3)
		if b_ok then
			t_cache:delete(s_codeKey)
			t_cache:delete(s_codeKey .. "_lock")
		else
			-- 等锁释放
			ngx.sleep(1)
		end
		n_val = t_cache:incr(s_codeKey, 1, -1)
		if n_val > (n_max or n_MAX_CODE) then
			log.day_list(MIS_TOOLS, "new_share_code overflow!", n_val)
			return nil, "share code overflow"
		end
	end

	local t_prefix_index = t_prefix[math.random(1, #t_prefix)]
	if not t_prefix_index or not t_prefix_index.value then
		return nil, "share_cfg error"
	end

	local s_prefix = t_prefix[math.random(1, #t_prefix)].value
	
	return s_prefix .. (mis_env.host_id or 0) .. (to_uint36(n_val, 5) or "nil")
end

--请求分享码
M.get_secret_code = function (s_type, uin, share_cfg)
	local b_ok, s_desc = t_cache:add(s_type.."_request_code" .. uin, 1, 0.2)
	if not b_ok then
		return t_retCode.n_SYS_ERR, ('request frequently'..uu.to_str(s_desc))
	end

	local n_MAX_CODE = 60466175
	local n_INIT_CODE = 38032637
	local s_code, s_reason = new_share_code(s_type, share_cfg.codePrefix, n_INIT_CODE, n_MAX_CODE)
	if s_code == nil then
		return t_retCode.n_SYS_ERR, ("new_share_code error"..uu.to_str(s_reason))
	end

    local t_codeData = {ctime = uu.now(), uin = uin, exchange = 0, exchange_users = {}}
    b_ok, s_desc = mis_data.get_user(uin):set_secret_code(s_code, t_codeData, 1)
	if b_ok ~= t_retCode.n_OK then
		return b_ok, uu.to_str(s_desc)
	end

	return t_retCode.n_OK, s_code
end

--兑换分享码
M.exchange_secret_code = function (s_code, exchange_uin, exchange_user_data)
	if not s_code 
	or not exchange_uin 
	or not exchange_user_data
	or not exchange_user_data.task_list then 
		return t_retCode.n_SYS_ERR, 'param error'
	end

	local b_ok, t_codeData = mis_data.get_user(exchange_uin):get_secret_code(s_code)
	if b_ok ~= t_retCode.n_OK then
		return b_ok, ("get_secret_code error |uin="..exchange_uin.." |t_codeData="..uu.to_str(t_codeData) )
	end

	if not t_codeData then 
		return t_retCode.n_CONCERT_CODE_INVALID, "t_codeData not exists"
	end

	-- 密码不存在
	if t_codeData.uin == nil then
		return t_retCode.n_CONCERT_CODE_INVALID, "code not exists"
	end

	if t_codeData.uin == exchange_uin then
		return t_retCode.n_CONCERT_CODE_SELF_LIMIT, "can't exchange self"
	end

	-- 只能兑换2次
	if t_codeData.exchange >= 2 then
		return t_retCode.n_CONCERT_CODE_EXCESS, "exchange excess"
	end
	
	-- 一个用户同一个码只能兑换一次
	if t_codeData.exchange_users and t_codeData.exchange_users[exchange_uin] then
		return t_retCode.n_CONCERT_CODE_REPEATED, "exchange repeated"
	end

	-- 已过期
	if os.date("%Y%m%d", t_codeData.ctime) ~= os.date("%Y%m%d") then
		return t_retCode.n_CONCERT_CODE_EXPIRE, "code expire"
	end

	--密码数据赋值
	t_codeData.exchange = t_codeData.exchange + 1
	t_codeData.exchange_users = t_codeData.exchange_users or {}
	t_codeData.exchange_users[exchange_uin] = 1

	--exchange_uin和share_uin都时读的同一个s_code
	local ok, s_desc = mis_data.get_user():set_secret_code(s_code, t_codeData)
	if ok ~= t_retCode.n_OK then
		return b_ok, ("save sys code error |uin="..exchange_uin.." |s_desc="..uu.to_str(s_desc))
	end

	--使用者更新任务
	local rewards = {}
	for _, v  in pairs(exchange_user_data.task_list) do
		--28是使用分享码
		if v.event==28 and v.status==0 then 
			local ok2, one_rewards = M.update_task(28, 1, exchange_user_data)
			if ok2 then 
				table.insert(rewards, one_rewards)
			end
			break
		end
	end

	return t_retCode.n_OK, rewards, t_codeData.uin
end

--兑换商店
M.exchange_shop = function (shop_cfg, user_shop_data)
	local shop = {}
	local update_flag = false

	--查玩家avt数据
	local t_avt = M.query_owned_avatar_account()

	--
	for _, v in ipairs(shop_cfg) do
		local item = {}
		item.id = v.id
		item.propId = tonumber(v.propId)
		item.propNum = tonumber(v.propNum)
		item.cost = tonumber(v.cost)
		item.itemName = v.itemName
		item.itemType = v.itemType
		item.status = M.EXCHANGE_SHOP.READY
		item.exCount = tonumber(v.exCount)
		item.countConf = tonumber(v.exCount)
		--item.currencyId  = tonumber(v.currencyId)
		local user_exchange_data = user_shop_data[tonumber(v.propId)] or 0
		
		--3是avt特殊处理下
		if v.itemType and tonumber(v.itemType)==3 then 
			local avt_id = M.item_to_avt(tonumber(v.propId)) 
			if t_avt[avt_id] then 
				if user_exchange_data~=1 then 
					update_flag = true
					user_shop_data[tonumber(v.propId)] = 1	
				end
				item.status = M.EXCHANGE_SHOP.ALREADY
			else
				if user_exchange_data~=0 then 
					update_flag = true
					user_shop_data[tonumber(v.propId)] = 0
				end
				item.status = M.EXCHANGE_SHOP.READY
			end
		else
			if (v.exCount) and tonumber(v.exCount) then 
				if (user_exchange_data >= tonumber(v.exCount))  then 
					item.exCount = 0
					item.status = M.EXCHANGE_SHOP.NO_COUNT
				else
					item.exCount = item.exCount - user_exchange_data
				end
			end	
		end

		item.count = user_shop_data[tonumber(v.propId)] or 0
		table.insert(shop, item)
	end

	return shop, update_flag
end

--物品ID转皮肤ID
M.item_to_skin = function (item_id)
	if not item_id or (not tonumber(item_id)) then 
		log.day_list(MIS_TOOLS, "[ERROR] item_to_skin item_id==nil!")
		return nil
	end

	local t_item_use_skin_def_config = mis_data.t_item_use_skin_def_config.t_conf 
	if (not t_item_use_skin_def_config) 
	or (not t_item_use_skin_def_config[tonumber(item_id)]) 
	or (not t_item_use_skin_def_config[tonumber(item_id)].SkinID) then 
		--log.day_list(MIS_TOOLS, "[ERROR] item_to_skin config error!", uu.to_str(t_item_use_skin_def_config))
		return nil
	end

	local skin_id = tonumber(t_item_use_skin_def_config[tonumber(item_id)].SkinID)

	return skin_id
end

--物品ID转avt的id
M.item_to_avt = function (item_id)
	if not item_id or (not tonumber(item_id)) then 
		log.day_list(MIS_TOOLS, "[ERROR] item_to_skin item_id==nil!")
		return nil
	end

	local t_item_use_skin_def_config = mis_data.t_item_use_skin_def_config.t_conf 
	if (not t_item_use_skin_def_config) 
	or (not t_item_use_skin_def_config[tonumber(item_id)]) 
	or (not t_item_use_skin_def_config[tonumber(item_id)].ModelID) then 
		--log.day_list(MIS_TOOLS, "[ERROR] item_to_skin config error!", uu.to_str(t_item_use_skin_def_config))
		return nil
	end

	local avt = tonumber(t_item_use_skin_def_config[tonumber(item_id)].ModelID)

	return avt
end

--=================================== 商业化相关 start=====================================
local MIS_CALL = "mis_call"

--local url = ns_config.gm_cost_server_url[ (uin_ % #ns_config.gm_cost_server_url) + 1 ]
local function call_account(uin_, params_)
	local url_ = mis_config.t_accountAddr.t_gm[uin_ % #mis_config.t_accountAddr.t_gm + 1]
	--log.debug( "call_account url=" .. url_ )

	local url_str_ = ""
	for k, v in pairs(params_) do
		url_str_ = url_str_ .. k .. '=' .. ngx.escape_uri(v) .. '&'
	end

	local md5_seed_ = uu.getStringBySortKey( params_ ) .. '522e6ccde68f4584a3282ce0b0eca1cf'

	local url_all_ = url_ .. '?' .. url_str_ .. 'sign=' .. ngx.md5( md5_seed_ )

	local ret, body = ns_network.getHttpPage( url_all_ )
	--log.debug("ret="..ret)
	--log.debug("body="..body)

	if  ret == 200 then
		return uu.json2table( body )
	else
		return {}
	end
end

local function call_profile(params_)
	local url_ = mis_config.s_profile_url

	local url_str_ = ""
	for k, v in pairs(params_) do
		url_str_ = url_str_ .. k .. '=' .. ngx.escape_uri(v) .. '&'
	end

	local md5_seed 	= params_.uin .. '#LY1006#' .. params_.time .. '#' .. params_.cmd .. '#'

	local url_all_ = url_ .. url_str_ .. 'token=' .. ngx.md5( md5_seed )

	local ret, body = ns_network.getHttpPage( url_all_ )
	
	if  ret == 200 then
		return uu.safe_loadstring( body )
	else
		log.day_list(MIS_CALL, "[ERROR] call_profile fail!", "ret="..ret, "body="..(body or "nil"))
		return {}
	end
end

local function call_mall(params_)
	local url_ = mis_config.t_mall_url

	local url_str_ = ""
	for k, v in pairs(params_) do
		url_str_ = url_str_ .. k .. '=' .. ngx.escape_uri(v) .. '&'
	end

	local md5_seed = (params_.time .. '#miniw_907#' .. params_.uin)
	--local md5_seed 	= params_.uin .. '#LY1006#' .. params_.time .. '#' .. params_.cmd .. '#'

	local url_all_ = url_ .. url_str_ .. 'auth=' .. ngx.md5( md5_seed )

	local ret, body = ns_network.getHttpPage( url_all_ )
	
	if  ret == 200 then
		return uu.safe_loadstring( body )
	else
		log.day_list(MIS_CALL, "[ERROR] call_profile fail!", "ret="..ret, "body="..(body or "nil"))
		return {}
	end
end

local function call_business(params_)
	local url_ = mis_config.t_business_url

	local url_str_ = ""
	for k, v in pairs(params_) do
		url_str_ = url_str_ .. k .. '=' .. ngx.escape_uri(v) .. '&'
	end

	local md5_seed = (params_.time .. '#miniw_907#' .. params_.uin)
	--local md5_seed 	= params_.uin .. '#LY1006#' .. params_.time .. '#' .. params_.cmd .. '#'

	local url_all_ = url_ .. url_str_ .. 'auth=' .. ngx.md5( md5_seed )

	local ret, body = ns_network.getHttpPage( url_all_ )
	
	if  ret == 200 then
		return uu.safe_loadstring( body )
	else
		log.day_list(MIS_CALL, "[ERROR] call_profile fail!", "ret="..ret, "body="..(body or "nil"))
		return {}
	end
end

local function private_query_path(uin_, path_ )
    --log.debug( "call private_query_path, path_=" .. path_ .. ", uin=" .. uin_ );
    local tmp_ = {
        cmd      = "query",
        path     = path_,
        uin      = uin_,
        ts       = uu.now(),
        ignore   = 1,
    };

    local ret_ = call_account(uin_, tmp_ );
	
    --log.day_list("account_query_path" , "private_query_path: " .. uu.table2json(ret_) .. " uin:" .. uin_)
    if ret_.return_code and ret_.return_code=='SUCCESS' then
        log.debug( "call gm_cost_ok" );
        local fret_ = ret_.return_result or {};
        return fret_;
    else
		log.day_list(MIS_CALL, "[ERROR] private_query_path call_account fail!"
			, "uin="..uin_, "private_query_path ret=" .. uu.table2json(ret_) )
        return {};
    end
end

--[[
skin_list:
	{[1]={["ExpireTime"]=-1,["SkinID"]=4},[2]={["ExpireTime"]=-1,["SkinID"]=1},
	[3]={["ExpireTime"]=1655455918,["SkinID"]=192},}(table)
]]

-- 查询账号的皮肤
M.query_skin_account = function(uin)
	uin = uin or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_skin_account fail. uin is invaild!", uin)
		return false
	end

	local ct_ret_ = private_query_path(uin, 'Account.BillDataSvr')
	local skin_list = {}
	if  type(ct_ret_)=='table' and type(ct_ret_["Account.BillDataSvr"]) == 'table' then
        if  ct_ret_["Account.BillDataSvr"].RoleSkinInfo and type(ct_ret_["Account.BillDataSvr"].RoleSkinInfo) == 'table' then
			skin_list = ct_ret_["Account.BillDataSvr"].RoleSkinInfo
        end
	end
	ct_ret_ = private_query_path( uin, "leveldb.RoleSkinInfo" )
	if  type(ct_ret_)=='table' and type(ct_ret_["leveldb.RoleSkinInfo"]) == 'table' then
		local leveldb_skin_list = ct_ret_["leveldb.RoleSkinInfo"]
		for k,v in pairs(leveldb_skin_list) do
            if type(v) == 'table' then
    			table.insert(skin_list,v)
            end
		end
	end

	return skin_list
end

-- 查询账号的永久皮肤
M.query_owned_skin_account = function(uin)
	uin = tonumber(uin) or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_owned_skin_account fail. uin is invaild!", uin)
		return false
	end


	local ct_ret_ = private_query_path(uin, 'Account.BillDataSvr')
	local skin_list = {}
	if  type(ct_ret_)=='table' and type(ct_ret_["Account.BillDataSvr"]) == 'table' then
        if  ct_ret_["Account.BillDataSvr"].RoleSkinInfo and type(ct_ret_["Account.BillDataSvr"].RoleSkinInfo) == 'table' then
			skin_list = ct_ret_["Account.BillDataSvr"].RoleSkinInfo
        end
	end
	ct_ret_ = private_query_path( uin, "leveldb.RoleSkinInfo" )
	if  type(ct_ret_)=='table' and type(ct_ret_["leveldb.RoleSkinInfo"]) == 'table' then
		local leveldb_skin_list = ct_ret_["leveldb.RoleSkinInfo"]
		for k,v in pairs(leveldb_skin_list) do
            if type(v) == 'table' then
    			table.insert(skin_list,v)
            end
		end
	end

	local owend_skin_list = {}
	for _, roleskin in pairs(skin_list) do
		if type(roleskin) == "table" and tonumber(roleskin.ExpireTime) == -1 then --永久
			table.insert(owend_skin_list, roleskin)
		end
	end

	return owend_skin_list
end

-- 查询是否拥有该皮肤
M.query_have_skin= function(uin, skin_id)
	uin = uin or 0
	skin_id = skin_id or 0
	if uin <= 0 or skin_id <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_have_skin fail. uin or skin_id is invaild!", uin, skin_id);
		return false
	end
	
	local ct_ret_ = private_query_path(uin, 'Account.BillDataSvr')
	local skin_list = {}
	if  type(ct_ret_)=='table' and type(ct_ret_["Account.BillDataSvr"]) == 'table' then
        if  ct_ret_["Account.BillDataSvr"].RoleSkinInfo and type(ct_ret_["Account.BillDataSvr"].RoleSkinInfo) == 'table' then
			skin_list = ct_ret_["Account.BillDataSvr"].RoleSkinInfo
        end
	end
	ct_ret_ = private_query_path( uin, "leveldb.RoleSkinInfo" )
	if  type(ct_ret_)=='table' and type(ct_ret_["leveldb.RoleSkinInfo"]) == 'table' then
		local leveldb_skin_list = ct_ret_["leveldb.RoleSkinInfo"]
		for k,v in pairs(leveldb_skin_list) do
            if type(v) == 'table' then
    			table.insert(skin_list,v)
            end
		end
	end

	for _, v in pairs(skin_list) do
		if tonumber(v.SkinID)==tonumber(skin_id) then 
			return true
		end
	end

	return false
end

--! @brief 获取账号服所有道具
M.query_account_items = function(uin)
	uin = tonumber(uin) or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_account_item_num fail. uin is invaild!", uin)
		return false
	end
	
	local tmp_ = {
		cmd      = "query",
		path     = "Account.ItemInfo",
		uin      = uin,
		ts       = uu.now(),
		-- ignore   = 1,
	};
	local ret_ = call_account(uin, tmp_ )

	if not ret_ 
	or (not ret_.return_code) 
	or (ret_.return_code~='SUCCESS')
	or (not ret_.return_result) then
		log.day_list(MIS_CALL, "[ERROR] query_account_item_num fail!"
			, "uin="..uin, "ret=" .. uu.to_str(ret_) )
		return nil
	end
	
	local items = ret_.return_result["Account.ItemInfo"] or {}

	return items
end

--! @brief 获取账号服某个道具数量
M.query_account_item_num = function(uin, id)
	uin = tonumber(uin) or 0
	id = tonumber(id) or 0
	if uin <= 0 or id <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_account_item_num fail. uin or id is invaild!", uin, id)
		return 0
	end
	
	local tmp_ = {
		cmd      = "query",
		path     = "Account.ItemInfo",
		uin      = uin,
		ts       = uu.now(),
		-- ignore   = 1,
	};
	local ret_ = call_account(uin, tmp_ )

	if not ret_ 
	or (not ret_.return_code) 
	or (ret_.return_code~='SUCCESS')
	or (not ret_.return_result) then
		log.day_list(MIS_CALL, "[ERROR] query_account_item_num fail!"
			, "uin="..uin, "ret=" .. uu.to_str(ret_) )
		return 0
	end
	
	local num = 0
	for _, v in pairs(ret_.return_result["Account.ItemInfo"] or {}) do
		if id==v.ItemID then 
			num = v.Num
		end
	end

	return num
end

--! @ 查询 已拥有的永久头像框
--! @ 返回： {id，id1,...}
M.query_profile_head_frame = function(uin_)
    uin_ = uin_ or ngx.ctx.m_params.uin
    local result = call_profile({
		uin = uin_,
        cmd      = "getProfile",
		time = uu.now(),
        op_uin   = uin_,
    })
   
	log.debug("head_frame="..uu.to_str(result))
	local head_frames 
	if result and result.profile then 
		head_frames = result.profile.head_frames
	end

    return head_frames
end

-- 查询avatar / ExpireTime==-1 为永久
M.query_owned_avatar_account = function(uin)
	uin = uin or ngx.ctx.m_params.uin
	if not uin then 
		return {}
	end

	local url = ''
	if #mis_config.t_accountAddr.query_avatar_url == 1 then
		url = mis_config.t_accountAddr.query_avatar_url[1]
	else
		local index = math.random( 1, #mis_config.t_accountAddr.query_avatar_url )
		url = mis_config.t_accountAddr.query_avatar_url[index]
	end
	url = url .. 'uin=' .. uin .. '&reqfield=ModelID,ExpireTime'
	local headers_ = 
		{ ["Connection"]="Keep-Alive",
		["User-Agent"]="PostmanRuntime/7.26.5",}

	local ret, body = ns_network.getHttpPage( url ,headers_);
	if  ret == 200 then
		local avatar = uu.json2table( body )
		if avatar and avatar.code == 0  then
			--log.debug("query avatar cache ok")
			--改成以[ModelID]为索引的结构
			local ret_t = {}
			if avatar.msg then 
				for _, v in pairs(avatar.msg) do
					ret_t[v.ModelID] = {}
					ret_t[v.ModelID].ExpireTime = v.ExpireTime
					ret_t[v.ModelID].ModelID = v.ModelID
				end

			end

			return ret_t
		end
	end
	return {}
end


--! @brief 获取账号服坐骑信息
M.query_owned_mount_account = function(uin)
	uin = tonumber(uin) or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_owned_mount_account fail. uin is invaild!", uin)
		return false
	end
	
	local tmp_ = {
		cmd      = "query_rider_all",
		uin      = uin,
		ts       = uu.now(),
		-- ignore   = 1,
	};
	local ret_ = call_account(uin, tmp_ )

	log.debug("ret_="..uu.to_str(ret_))

	if not ret_ 
	or (not ret_.return_code) 
	or (ret_.return_code~='SUCCESS')
	or (not ret_.return_result) then
		log.day_list(MIS_CALL, "[ERROR] query_owned_mount_account fail!"
			, "uin="..uin, "ret=" .. uu.to_str(ret_) )
		return nil
	end
	
	--[[
	local mount_list = {}
    for _, info in pairs(ret_.return_result or {}) do
        for i=0, info.RiderLevel or 0 do
            mount_list[#mount_list + 1] = info.RiderID + i
        end
    end
	--]]

	return ret_.return_result
end

--! @brief 获取mall服武器信息
M.query_all_weponskins = function(uin)
	uin = tonumber(uin) or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_owned_mount_account fail. uin is invaild!", uin)
		return false
	end
	
	local tmp_ = {
		cmd     = "query_all_weponskins",
		uin      = uin,
		time     = uu.now(),
		-- ignore   = 1,
	};
	local ret_ = call_mall(tmp_ )

	log.debug("ret_="..uu.to_str(ret_))

	if not ret_ 
	or (not ret_.data) 
	or (ret_.ret~=0) then
		log.day_list(MIS_CALL, "[ERROR] query_all_weponskins fail!"
			, "uin="..uin, "ret=" .. uu.to_str(ret_) )
		return nil
	end

	return ret_.data
end

--! @brief 获取business服喷漆数据
M.query_all_spraypaints = function(uin)
	uin = tonumber(uin) or 0
	if uin <= 0 then 
		log.day_list( MIS_CALL, "[ERROR] query_owned_mount_account fail. uin is invaild!", uin)
		return false
	end
	
	local tmp_ = {
		cmd     = "query_all_spraypaints",
		uin      = uin,
		time     = uu.now(),
		-- ignore   = 1,
	};
	local ret_ = call_business(tmp_ )

	log.debug("ret_="..uu.to_str(ret_))

	if not ret_ 
	or (not ret_.data) 
	or (ret_.ret~=0) then
		log.day_list(MIS_CALL, "[ERROR] query_all_weponskins fail!"
			, "uin="..uin, "ret=" .. uu.to_str(ret_) )
		return nil
	end

	return ret_.data
end


--扣迷你币
M.private_change_minicoin = function( uin_, num_, reason_,ext)

    reason_ = reason_ or "";
    log.debug( "call private_change_minicoin, cc=" .. num_ .. ", " .. reason_ );

    if  num_ >= 0 then
        return true
    end

    --扣费
    local req_ = {
        cmd      = "minicoin_rm",       --减迷你币
        minicoin = 0 - num_,
        uin      = uin_,
        ts       = uu.now(),
        reason   = reason_,
        ext    = type(ext) == "table" and uu.table2json(ext) or ext,
    };

    log.day_list( "business_cost", "minicoin_begin", ngx.ctx.m_params.uin, num_, reason_);

	local ret = call_account(uin_, req_ )
    --local ret = ns_account.private_call_gm_comm(req_)
    if ret and ret.return_code ~= 'SUCCESS' then
        log.day_list( MIS_TOOLS, "minicoin_fail", ngx.ctx.m_params.uin, num_, reason_,uu.to_str(ret));
        return false
    end

    log.day_list( MIS_TOOLS, "minicoin_ok", ngx.ctx.m_params.uin, num_, reason_,uu.to_str(ret) );

    local rm_from_money = 0
    if  ret.return_result and  type(ret.return_result) == 'table' and  type(ret.return_result.rm_from_money) == 'boolean'then
        rm_from_money = ret.return_result.rm_from_money and 1 or 0
    end

    return true,rm_from_money;
end

--=================================== 商业化相关 end=====================================

--=================================== tools start=====================================
M.set_nginx_share = function(cache_name, key, value, exptime)

	if not cache_name or type(cache_name) ~= "string" then
		log.day_list(MIS_TOOLS,"set_nginx_share:cache_name not a string", uu.to_str(cache_name))
		return false
	end

	if not key or not value then
		log.day_list(MIS_TOOLS,"set_nginx_share:key or value is nil ", uu.to_str(cache_name), uu.to_str(key), tosuu.to_strtring(value))
		return false
	end

	if type(value) ~= "table" then
		log.day_list(MIS_TOOLS,"set_nginx_share:value not a table", uu.to_str(cache_name), type(value))
		return false
	end

	local cache_data = ngx.shared[cache_name]
	if not cache_data then
		log.day_list(MIS_TOOLS,"set_nginx_share:cache_data not exist", uu.to_str(cache_name), uu.to_str(key))
		return false
	end

	--最长24小时
	local max_time = 24 * 3600
	local succ, err, forcible = cache_data:set(key, uu.table2json(value), tonumber(exptime) or max_time)
	if not succ then
		log.day_list(MIS_TOOLS,"set_nginx_share:set fatal", uu.to_str({cache_name = cache_name, key = key, err = err, forcible = forcible}))
		return false
	end

	return true
end

M.get_nginx_share = function(cache_name, key)
	if not cache_name or type(cache_name) ~= "string" then
		log.day_list(MIS_TOOLS,"get_nginx_share:cache_name not a string", uu.to_str(cache_name))
		return false
	end

	if not key  then
		log.day_list(MIS_TOOLS,"get_nginx_share: not key ", uu.to_str(cache_name), type(key))
		return false
	end

	local cache_data = ngx.shared[cache_name]
	if not cache_data then
		log.day_list(MIS_TOOLS,"get_nginx_share:cache_data not exist", uu.to_str(cache_name), uu.to_str(key))
		return false
	end

	local cache_string = cache_data:get(key)
	if not cache_string then
		log.day_list(MIS_TOOLS,"get_nginx_share:cache_string not exist", uu.to_str(cache_name), uu.to_str(key))
		return true
	end

	if type(cache_string) ~= "string" then
		log.day_list(MIS_TOOLS,"get_nginx_share:cache_string not a string", uu.to_str(cache_name), type(cache_string))
		return false
	end

	local cache_table = uu.json2table(cache_string)
	if not cache_table or type(cache_table) ~= "table" then
		log.day_list(MIS_TOOLS,"get_nginx_share:cache_data not a table", uu.to_str(cache_name), type(cache_data))
		return false
	end

	return true, cache_table
end

-------------------------------------------------------------------------------------------------------------
-- 接口函数	: clear_nginx_share
-- 说明		: 清除共享内存
-- 参数  	: cache_name	缓存名
-- 返回值	: 无
-------------------------------------------------------------------------------------------------------------
M.clear_nginx_share = function(cache_name)
	if not cache_name or type(cache_name) ~= "string" then
		log.day_list(MIS_TOOLS,"clear_nginx_share:cache_name not a string", tostring(cache_name))
		return false
	end

	local cache_data = ngx.shared[cache_name]
	if not cache_data then
		log.day_list(MIS_TOOLS,"clear_nginx_share:cache_data not exist", tostring(cache_name))
		return false
	end

	cache_data:flush_all()

	return true
end

--=================================== tools end=====================================

return M